/*
 * Copyright (C) 2024, Ingenic Semiconductor Co.,Ltd.
 * Author: Keven <keven.ywhan@ingenic.com>
 */

#include <common.h>
#include <gpio.h>
#include <cpm.h>
#include <msc.h>

#define APP_CMD		(0x1<<5)

#define msc_readb(o)		readb(MSC0_BASE + (o))
#define msc_readw(o)		readw(MSC0_BASE + (o))
#define msc_readl(o)		readl(MSC0_BASE + (o))
#define msc_writeb(b, o)	writeb(b, MSC0_BASE + (o))
#define msc_writew(b, o)	writew(b, MSC0_BASE + (o))
#define msc_writel(b, o)	writel(b, MSC0_BASE + (o))

struct msc_config {
	char index;
	char bus_width;
	int highcap;
};

struct msc_config msc = {
	.index = CONFIG_MSC_INDEX,
	.bus_width = MSC_BUS_WIDTH,
	.highcap = 0,
};


static int cmd_err_stat_check(void)
{
	unsigned int index = msc.index;

	if(msc_readw(MSC_ERROR_INT_STAT_R(index))
			& (MSC_CMD_TOUT_ERR_STAT
				| MSC_CMD_CRC_ERR_STAT
				| MSC_CMD_END_BIT_ERR_STAT
				| MSC_CMD_IDX_ERR_STAT)) {

		msc_writew(0xffff, MSC_ERROR_INT_STAT_R(index));
		return -1;
	}

	return 0;
}

static int xfer_err_stat_check(void)
{
	unsigned int index = msc.index;

	if(msc_readw(MSC_ERROR_INT_STAT_R(index))
			& (MSC_DATA_TOUT_ERR_STAT
				| MSC_DATA_CRC_ERR_STAT
				| MSC_DATA_END_BIT_ERR_STAT)) {

		msc_writew(0xffff, MSC_ERROR_INT_STAT_R(index));
		return -1;
	}

	return 0;
}

static int wait_cmd_complete(void)
{
	unsigned int timeout = 1000;
	unsigned int index = msc.index;

	while(!(msc_readw(MSC_NORMAL_INT_STAT_R(index)) & MSC_CMD_COMPLETE_STAT)
			&& --timeout)
		udelay(10);

	msc_writew(MSC_CMD_COMPLETE_STAT, MSC_NORMAL_INT_STAT_R(index));

	if(!timeout) {
		serial_puts("cmd timeout err ...\n");
		return -1;
	}

	return 0;
}

static int wait_xfer_complete(void)
{
	unsigned int timeout = 1000;
	unsigned int index = msc.index;

	while(!(msc_readw(MSC_NORMAL_INT_STAT_R(index)) & MSC_XFER_COMPLETE_STAT)
			&& --timeout)
		udelay(10);

	msc_writew(MSC_XFER_COMPLETE_STAT, MSC_NORMAL_INT_STAT_R(index));

	if(!timeout) {
		serial_puts("xfer timeout err ...\n");
		return -1;
	}

	return 0;
}

static int wait_buf_rd(void)
{
	unsigned int timeout = 1000;
	unsigned int index = msc.index;

	while(!(msc_readw(MSC_NORMAL_INT_STAT_R(index)) & MSC_BUF_RD_READY_STAT)
			&& --timeout)
		udelay(10);

	msc_writew(MSC_BUF_RD_READY_STAT, MSC_NORMAL_INT_STAT_R(index));

	if(!timeout) {
		serial_puts("wait buf read state timeout err ...\n");
		return -1;
	}

	return 0;
}

static void msc_reset(int modules)
{
	unsigned int timeout = 1000;
	unsigned int index = msc.index;

	msc_writeb(modules, MSC_SW_RST_R(index));
	while((msc_readb(MSC_SW_RST_R(index)) & modules) && --timeout)
		udelay(10);

	if(!timeout)
		serial_puts("sw reset timeout err ....\n");
}

static void msc_card_clks_en(void)
{
	unsigned int timeout = 1000;
	unsigned int index = msc.index;

	msc_writew(MSC_INTERNAL_CLK_EN, MSC_CLK_CTRL_R(index)); /* ctrl sd/emmc model */
	while(!(msc_readw(MSC_CLK_CTRL_R(index)) & 0x2) && --timeout)
		udelay(10);

	if(!timeout)
		serial_puts("clk wait stable timeout ....\n");

	msc_writew(MSC_SD_CLK_EN | MSC_INTERNAL_CLK_EN, MSC_CLK_CTRL_R(index));
}

static void msc_int_status_en(void)
{
	unsigned int index = msc.index;

	msc_writew(0xffff, MSC_NORMAL_INT_STAT_EN_R(index));/* irq enable */
	msc_writew(0xffff, MSC_ERROR_INT_STAT_EN_R(index)); /* set irq error enable*/
}

static void msc_timeout_set(void)
{
	unsigned int index = msc.index;

	/* timeout counter = tmclk * 2^(13 + value); value < 0xf */
	msc_writeb(0xe, MSC_TOUT_CTRL_R(index)); /*delay control*/
}

static void sd_hardware_init()
{
	msc_reset(MSC_SW_RST_ALL); /*  reset mmc all */
	msc_int_status_en(); /*  init mmc*/
	msc_timeout_set(); /* set mmc timeout configure */
	msc_card_clks_en(); /*  set sd model or clk */
}

/**
 *	mmc_cmd - host send cmd to device
 *	@arg: command argument
 *	@cmdidx: command index
 *	@cmdtype: command type (00/01/10/11
 *		normal cmd / suspend cmd / resume cmd / abort command)
 *	@cmdat: data present select, if set indicates that
 *		data is present and shall be transferred useing DAT line.
 *	@subcmd: distinguishes a main command or a sub command
 *	@rtype: response type
 */
static unsigned char *mmc_cmd(unsigned int arg, unsigned int cmdidx, unsigned int cmdtype,
		unsigned int cmdat, unsigned int subcmd, unsigned int rtype)
{
	static unsigned int buf[2] = {0};
	unsigned int cmd = 0;
	unsigned int mask = 0;
	unsigned int tmp = 0, i = 0;
	unsigned char *resp = (unsigned char *)buf;
	unsigned int timeout = 1000;
	unsigned int index = msc.index;

	buf[0] = 0;
	buf[1] = 0;

	/* chear all int status */
	msc_writew(0xffff, MSC_NORMAL_INT_STAT_R(index));
	msc_writew(0xffff, MSC_ERROR_INT_STAT_R(index));

	/* check cmd & data line */
	mask = MSC_CMD_INHIBIT | MSC_DATA_INHIBIT;

	/* We shouldn't wait for data inihibit for stop commands, even
	   though they might use busy signaling */
	if (cmdidx == 12)
		mask &= ~MSC_DATA_INHIBIT;

	while ((msc_readl(MSC_PSTATE_REG(index)) & mask) && --timeout)
		udelay(10);

	if (timeout == 0) {
		serial_puts("Controller never released inhibit bit(s).\n");
		goto exit;
	}

	/* set arg & cmd */
	cmd |= (cmdidx & 0x3f) << 8;
	cmd |= (cmdtype & 0x3) << 6;
	cmd |= (cmdat & 0x1) << 5;
	cmd |= (subcmd & 0x1) << 2;

	switch (rtype) {
	case MSC_CMDAT_RESPONSE_NONE:
		cmd |= MSC_RESP_TYPE_SELECT_RESP_NO_RESP;
		break;
	case MSC_CMDAT_RESPONSE_R1:
	case MSC_CMDAT_RESPONSE_R3:
	case MSC_CMDAT_RESPONSE_R6:
	case MSC_CMDAT_RESPONSE_R7:
		cmd |= MSC_RESP_TYPE_SELECT_RESP_LEN_48;
		break;
	case MSC_CMDAT_RESPONSE_R1b:
		cmd |= MSC_RESP_TYPE_SELECT_RESP_LEN_48B;
		break;
	case MSC_CMDAT_RESPONSE_R2:
		cmd |= MSC_RESP_TYPE_SELECT_RESP_LEN_136;
		break;
	default:
		cmd |= MSC_RESP_TYPE_SELECT_RESP_LEN_48;
		break;
	}

	msc_writel(arg, MSC_ARGUMENT_R(index));
	msc_writew(cmd, MSC_CMD_R(index));

	if(wait_cmd_complete()) {
		serial_puts("cmd send can't wait complete ...\n");
		goto exit;
	}

	if(cmd_err_stat_check()) {
		serial_puts("command state check error ...\n");
		goto exit;
	}

	/* get response */
	tmp = msc_readl(MSC_RESP01_R(index));
	for (i = 0; i < 4; i++)
		resp[i + 1] = (tmp >> (i * 8)) & 0xff;

exit:
	/* reset cmd & data line */
	if(!cmdat)
		msc_reset(MSC_SW_RST_DAT | MSC_SW_RST_CMD);

	return resp;
}

#ifndef CONFIG_FPGA_TEST
static void msc_clk_div_set()
{
	unsigned int tmp;
	unsigned int timeout = 200;
	unsigned int off;
	unsigned int clk_set = 0, clkrt = 0;

	clk_set = CONFIG_EXTAL_CLK_HZ / 2;
	while (200000 < clk_set) {
		clkrt++;
		clk_set >>= 1;
	}

	if (clkrt > 7)
		clkrt = 7;

	if(n == 0)
		off = CPM_MSC0CDR;
	else
		off = CPM_MSC1CDR;

	/* Enable msc extclk */
	tmp = readl(CPM_MSC0CDR) | MSCCDR_EXCK_E;
	writel(tmp, CPM_MSC0CDR);

	tmp = readl(off);
	tmp &= ~(MSCCDR_DIV_MASK | MSCCDR_MPCS_MASK);
	tmp |= (MSCCDR_DIV_MASK & (div)) | MSCCDR_CE | MSCCDR_MPCS_EXCLK;

	writel(tmp, off);
	while((readl(off) & MSCCDR_BUSY) && --timeout)
		udelay(10);
	if(!timeout)
		serial_puts("MSCCDR can't wait busy \n");

	tmp &= ~MSCCDR_CE;
	writel(tmp, off);
}
#endif

static void msc_sync_abort(void)
{
	unsigned int index = msc.index;

	msc_writeb(MSC_STOP_BG_REQ, MSC_BGAP_CTRL_R(index));

	if(wait_xfer_complete())
		serial_puts("stop bg req err, can't wait xfer complete ...\n");

	/* CMD12 Forces the Device to stop transmission */
	mmc_cmd(0, 12, 3, 0, 0, MSC_CMDAT_RESPONSE_R1);

	msc_reset(MSC_SW_RST_DAT | MSC_SW_RST_CMD);
}

static int sd_found(void)
{
	unsigned char *resp;
	unsigned int cardaddr;
	int rca;
	unsigned int timeout = 100;

	serial_puts("sd found ...\n");

	/* CMD55 indicates to the card that the next CMD is ACMD */
	resp = mmc_cmd(0, 55, 0, 0, 0, MSC_CMDAT_RESPONSE_R1);

	/**
	 * ACMD41 send host capacity support information (hcs)
	 *        and asks the accessed card to send its
	 *        operating condition register (OCR) content
	 *        in the response on the CMD line
	 *
	 * arg :
	 * [23:0] vdd voltage (ocr[23:0])
	 * [24]	 s18r
	 * [28]	 xpc
	 * [30]	 hcs (ocr[30])
	 */
	resp = mmc_cmd(0x40ff8000, 41, 0, 0, 0, MSC_CMDAT_RESPONSE_R3);

	while (--timeout && !(resp[4] & 0x80)) {
		mdelay(10);
		resp = mmc_cmd(0, 55, 0, 0, 0, MSC_CMDAT_RESPONSE_R1);
		resp = mmc_cmd(0x40ff8000, 41, 0, 0, 0, MSC_CMDAT_RESPONSE_R3);
	}

	if (!timeout) {
		serial_puts("sd card init err,can't wait ocr[31] card power up status ...\n");
		return -1;
	}

	if(resp[4] & 0x40) {
		serial_puts("the sd card is a msc.highcap card\n");
		msc.highcap = 1;
	}

	/* CMD2 Asks any card to send the CID */
	resp = mmc_cmd(0, 2, 0, 0, 0, MSC_CMDAT_RESPONSE_R2);

	/* CMD3 Ask the card to publish a new relative address (RCA) */
	//0x7
	resp = mmc_cmd(0, 3, 0, 0, 0, MSC_CMDAT_RESPONSE_R6);
	cardaddr = (resp[4] << 8) | resp[3];
	rca = cardaddr << 16;

	serial_puts("rca : ");
	serial_puthex(rca);

	/* CMD7 card is selected by its own relative address */
	resp = mmc_cmd(rca, 7, 0, 0, 0, MSC_CMDAT_RESPONSE_R1b);

	resp = mmc_cmd(rca, 55, 0, 0, 0, MSC_CMDAT_RESPONSE_R1);

	/**
	 * ACMD6 set buswidth,
	 * arg :
	 * [1:0]  00/10  1bit/4bit
	 */
	if(msc.bus_width == MSC_BUS_WIDTH_4)
		resp = mmc_cmd(0x2, 6, 0, 0, 0, MSC_CMDAT_RESPONSE_R1);
	else
		resp = mmc_cmd(0x0, 6, 0, 0, 0, MSC_CMDAT_RESPONSE_R1);

#ifndef CONFIG_FPGA_TEST
	msc_clk_div_set(0, index);
#endif

	return 0;
}

static int mmc_found(void)
{
	unsigned char *resp;
	unsigned int timeout = 100;
	unsigned int tmp, buswidth_arg;
	int rca;
	int status = -1;

	serial_puts("mmc found ...\n");

	msc_sync_abort();

	/* CMD0 Resets all cards to idle state */
	resp = mmc_cmd(0, 0, 0, 0, 0, MSC_CMDAT_RESPONSE_NONE);

	/**
	 * CMD1 send ocr
	 * arg: ocr without busy
	 * [7]		1.70~1.95V
	 * [14:8]	2.0~2.6V
	 * [23:15]	2.7~3.6V
	 * [30:29]	Access_mode: 00/10  byte/sector mode
	 */
	resp = mmc_cmd(0x40ff8000, 1, 0, 0, 0, MSC_CMDAT_RESPONSE_R3);

	while (--timeout && !(resp[4] & 0x80)) {
		mdelay(10);
		resp = mmc_cmd(0x40ff8000, 1, 0, 0, 0, MSC_CMDAT_RESPONSE_R3);
	}

	if (!timeout) {
		serial_puts("emmc card init err ...\n");
		return -1;
	}

	if(resp[4] & 0x40) {
		serial_puts("the emmc card is a msc.highcap card\n");
		msc.highcap = 1;
	}

	/* CMD2 Asks any card to send the CID */
	resp = mmc_cmd(0, 2, 0, 0, 0, MSC_CMDAT_RESPONSE_R2);

	/* CMD3 Assigns relative address to the Device */
	rca = 0x10 << 16;
	resp = mmc_cmd(rca, 3, 0, 0, 0, MSC_CMDAT_RESPONSE_R1);

	/* CMD7 card is selected by its own relative address */
	resp = mmc_cmd(rca, 7, 0, 0, 0, MSC_CMDAT_RESPONSE_R1);

	/**
	 * CMD6 switch
	 * arg :
	 * [31:26] Set to 0
	 * [25:24] Access
	 *		00 ----  command set
	 *		01 ----  set bits
	 *		10 ----  clear bits
	 *		11 ----  write byte
	 * [23:16] Index
	 * [15:8]  Value
	 * [7:3]   Set to 0
	 * [2:0]   Cmd Set
	 */
	/* set buswidth */
	tmp = (msc.bus_width == MSC_BUS_WIDTH_1) ? 0 : 1;
	buswidth_arg = 0x3 << 24 | 183 << 16 | (tmp << 8) | 0x1;
	resp = mmc_cmd(buswidth_arg, 6, 0, 0, 0, MSC_CMDAT_RESPONSE_R1b);

	/**
	 * CMD13 send status
	 * arg :
	 * [31:16] RCA
	 * [15]	   SQS
	 * [14:1]  Stuff bits
	 * [0]     HPI
	 */
	/* wait device prg status */
	timeout = 1000;
	do {
		resp = mmc_cmd(rca, 13, 0, 0, 0, MSC_CMDAT_RESPONSE_R1);
		status = resp[1] | (resp[2] << 8) | (resp[3] << 16) | (resp[4] << 24);
		if ((status & (0xf << 9)) != (7 << 9))
			break;
		udelay(100);
	} while(--timeout);

#ifndef CONFIG_FPGA_TEST
	unsigned int index = msc.index;
	msc_clk_div_set(0, index);
#endif

	return 0;
}

static void msc_set_xfer_bus_width(void)
{
	unsigned int val;
	unsigned int index = msc.index;

	/* bus width */
	val = msc_readb(MSC_HOST_CTRL1_R(index));

	val &= ~(MSC_EXT_DAT_XFER_BIT | MSC_DAT_XFER_WIDTH_BIT);

	if(msc.bus_width == MSC_BUS_WIDTH_4)
		val |= MSC_DAT_XFER_WIDTH_BIT;

	msc_writeb(val, MSC_HOST_CTRL1_R(index));
}

static int  mmc_block_read(unsigned int start, unsigned int blkcnt, unsigned int *dst)
{
	unsigned int tmp = 0;
	unsigned int ret = 0;
	unsigned int cnt = 0;
	unsigned int nob = blkcnt;
	unsigned int index = msc.index;

	/* set block size and block count */
	msc_writew(512, MSC_BLOCKSIZE_R(index));
	msc_writew(nob, MSC_BLOCKCOUNT_R(index));
	msc_set_xfer_bus_width();

	/* CMD16 Sets the block length (in bytes) */
	mmc_cmd(512, 16, 0, 0, 0, MSC_CMDAT_RESPONSE_R1);

	/* set multiple block read and auto send CMD12 */
	tmp = MSC_MULTI_BLK_SEL | MSC_BLOCK_COUNT_ENABLE | MSC_DATA_XFER_DIR_RD | MSC_AUTO_CMD12_ENABLE;
	msc_writew(tmp, MSC_XFER_MODE_R(index));

	/**
	 * CMD18 multiple block read
	 * arg :
	 * SDSC : byte access
	 * SDHC/SDXC : sector access
	 */
	mmc_cmd((msc.highcap ? start : (start * 512)), 18, 0, 1, 0, MSC_CMDAT_RESPONSE_R1);
	for (; nob > 0; nob--) {
		cnt = 128;

		if(wait_buf_rd()) {
			serial_puts("data xfer can't wait buf read ready ...\n");
			ret = -1;
			goto exit;
		}

		while(cnt--) {
			*dst = msc_readl(MSC_BUF_DATA_R(index));
			dst++;
		}
	}

	if(wait_xfer_complete()) {
		serial_puts("data xfer can't wait complete ...\n");
		ret = -1;
		goto exit;
	}

	if(xfer_err_stat_check()) {
		serial_puts("xfer state check error ...\n");
		ret = -1;
		goto exit;
	}

exit:
	msc_reset(MSC_SW_RST_DAT | MSC_SW_RST_CMD);

	return ret;
}


void sd_init()
{
	unsigned char *resp;
	unsigned int ret = 0;
	unsigned int index = msc.index;

#ifndef CONFIG_FPGA_TEST
	msc_clk_div_set();
#endif

	switch(index) {
	case 0:
		if(msc.bus_width == MSC_BUS_WIDTH_4)
			__gpio_b_as_msc0_4bit();
		else
			__gpio_b_as_msc0_1bit();
		__cpm_start_msc0();
		break;
	case 1:
	default:
		if(msc.bus_width == MSC_BUS_WIDTH_4)
			__gpio_b_as_msc0_4bit();
		else
			__gpio_b_as_msc0_1bit();
		__cpm_start_msc0();
		break;
	}

	sd_hardware_init();

	/* CMD0 Resets all cards to idle state */
	resp = mmc_cmd(0, 0, 0, 0, 0, MSC_CMDAT_RESPONSE_NONE);

	/**
	 * CMD8 send sd memory card interface condition
	 * arg :
	 * [7:0]  check pattern
	 * [11:8] supply voltage (VHS)
	 */
	resp = mmc_cmd(0x1aa, 8, 0, 0, 0, MSC_CMDAT_RESPONSE_R7); /*check sd model & voltage*/

	resp = mmc_cmd(0, 55, 0, 0, 0, MSC_CMDAT_RESPONSE_R1);/* send special cmd  acmd*/

	if (resp[1] & APP_CMD) {
		resp = mmc_cmd(0x40ff8000, 41, 0, 0, 0, MSC_CMDAT_RESPONSE_R3);

		/* if *(unsigned int*)resp == 0, mean acmd41 timeout or non voltage supprot */
		if(*(unsigned int *)resp) {
			serial_puts("sd found\n");
			ret = sd_found();
		} else {
			ret = mmc_found();
			serial_puts("mmc found\n");
		}
	} else {
		ret = mmc_found();
		serial_puts("mmc found\n");
	}
	if (ret) {
		serial_puts("card init err ...\n");
		while(1); // TODO
	}
}

int sd_block_read(unsigned int start_blk, unsigned int blk_cnt, void *buf)
{
	return mmc_block_read(start_blk, blk_cnt, (unsigned int *)buf);
}
